﻿using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Net.Http;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;
using Windows.System.Threading;
using Windows.UI.Text;
using Windows.Web;
using SocketIOClient.WinRT.Helpers;
using SocketIOClient.WinRT.Messages;

namespace SocketIOClient.WinRT
{
    /// <summary>
    /// Class to emulate socket.io javascript client capabilities for .net classes
    /// </summary>
    /// <exception cref = "ArgumentException">Connection for wss or https urls</exception>  
    public class Client : IDisposable, IClient
    {
        private string _userId;
        private string _password;

        private ThreadPoolTimer _socketHeartBeatTimer; // HeartBeat timer 
        private readonly Task _dequeuOutBoundMsgTask;
        private BlockingCollection<string> _outboundQueue;
        private int _retryConnectionCount;
        private int _retryConnectionAttempts = 3;
        //private readonly static object padLock = new object(); // allow one connection attempt at a time

        /// <summary>
        /// Uri of Websocket server
        /// </summary>
        protected Uri Uri;
        /// <summary>
        /// Underlying WebSocket implementation
        /// </summary>
        protected MessageWebSocket WsClient;
        /// <summary>
        /// RegistrationManager for dynamic events
        /// </summary>
        protected RegistrationManager RegistrationManager;  // allow registration of dynamic events (event names) for client actions

        // Events
        /// <summary>
        /// Opened event comes from the underlying websocket client connection being opened.  This is not the same as socket.io returning the 'connect' event
        /// </summary>
        public event EventHandler Opened;
        public event EventHandler<MessageEventArgs> Message;
        public event EventHandler ConnectionRetryAttempt;
        public event EventHandler HeartBeatTimerEvent;
        /// <summary>
        /// <para>The underlying websocket connection has closed (unexpectedly) and reconnect attempts have failed.</para>
        /// <para>The Socket.IO service may have closed the connection due to a heartbeat timeout, or the connection was just broken</para>
        /// <para>Call the client.Connect() method to re-establish the connection</para>
        /// </summary>
        public event EventHandler SocketConnectionClosed;
        public event EventHandler<ErrorEventArgs> Error;

        /// <summary>
        /// ResetEvent for Outbound MessageQueue Empty Event - all pending messages have been sent
        /// </summary>
        public ManualResetEvent MessageQueueEmptyEvent = new ManualResetEvent(true);

        /// <summary>
        /// Connection Open Event
        /// </summary>
        public ManualResetEvent ConnectionOpenEvent = new ManualResetEvent(false);


        /// <summary>
        /// Number of reconnection attempts before raising SocketConnectionClosed event - (default = 3)
        /// </summary>
        public int RetryConnectionAttempts
        {
            get { return this._retryConnectionAttempts; }
            set { this._retryConnectionAttempts = value; }
        }

        /// <summary>
        /// Value of the last error message text  
        /// </summary>
        public string LastErrorMessage = "";

        /// <summary>
        /// Represents the initial handshake parameters received from the socket.io service (SID, HeartbeatTimeout etc)
        /// </summary>
        public SocketIOHandshake HandShake { get; private set; }

        /// <summary>
        /// Returns boolean of ReadyState == WebSocketState.Open
        /// </summary>
        public bool IsConnected
        {
            get
            {
                return this.ReadyState == WebSocketState.Open;
            }
        }

        /// <summary>
        /// Connection state of websocket client: None, Connecting, Open, Closing, Closed
        /// </summary>
        public WebSocketState ReadyState { get; protected set; }

        //private FixReadyStateAfterException(Exception ex)
        //{
        //    WebErrorStatus status = WebSocketError.GetStatus(ex.GetBaseException().HResult);
        //    switch (status)
        //    {
        //        case WebErrorStatus.CannotConnect:
        //            case WebErrorStatus.BadGateway:
        //            case WebErrorStatus.ConnectionAborted:
        //            case WebErrorStatus.Disconnected:
        //            case WebErrorStatus.InternalServerError:
        //            ReadyState = WebSocketState.Closed;
        //            break;
        //    }
        //}

        // Constructors
        public Client(string url)
        {
            this.Uri = new Uri(url);

            this.RegistrationManager = new RegistrationManager();
            this._outboundQueue = new BlockingCollection<string>(new ConcurrentQueue<string>());
            this._dequeuOutBoundMsgTask = Task.Factory.StartNew(
                async () =>
                {
                    await DequeuOutboundMessages();
                },
                TaskCreationOptions.LongRunning);
        }

        /// <summary>
        /// Initiate the connection with Socket.IO service
        /// </summary>
        public async Task ConnectAsync(string userId = null, string password = null)
        {
            //lock (padLock)
            //{
            if (this.ReadyState == WebSocketState.Connecting || this.ReadyState == WebSocketState.Open)
            {
                this.OnErrorEvent(this,
                    new ErrorEventArgs("Can't call Connect() while ReadyState is Connecting or Open.", null));
                this.ReadyState = WebSocketState.Closed;
            }
            else
            {
                try
                {
                    _userId = userId;
                    _password = password;

                    this.ConnectionOpenEvent.Reset();
                    this.HandShake = await this.RequestHandshakeAsync(Uri);
                    // perform an initial HTTP request as a new, non-handshaken connection

                    if (this.HandShake == null || string.IsNullOrWhiteSpace(this.HandShake.SID) ||
                        this.HandShake.HadError)
                    {
                        this.LastErrorMessage = string.Format("Error initializing handshake with {0}", Uri);
                        this.OnErrorEvent(this, new ErrorEventArgs(this.LastErrorMessage, new Exception()));
                    }
                    else
                    {
                        //string wsScheme = (uri.Scheme == Uri.UriSchemeHttps ? "wss" : "ws");
                        //this.wsClient = new WebSocket(
                        //    string.Format("{0}://{1}:{2}/socket.io/1/websocket/{3}", wsScheme, uri.Host, uri.Port, this.HandShake.SID),
                        //    string.Empty,
                        //    this.socketVersion);
                        //this.wsClient.EnableAutoSendPing = false; // #4 tkiley: Websocket4net client library initiates a websocket heartbeat, causes delivery problems
                        //this.wsClient.Opened += this.wsClient_OpenEvent;
                        //this.wsClient.MessageReceived += this.wsClient_MessageReceived;
                        //this.wsClient.Error += this.wsClient_Error;

                        //this.wsClient.Closed += wsClient_Closed;

                        //this.wsClient.Open();

                        string wsScheme = (Uri.Scheme == "https" ? "wss" : "ws");
                        var tmpClient = new MessageWebSocket();
                        //tmpClient.Control.ServerCredential.UserName = _userId;
                        //tmpClient.Control.ServerCredential.Password = _password;
                        tmpClient.Control.MessageType = SocketMessageType.Utf8;
                        tmpClient.MessageReceived += this.MessageReceived;
                        tmpClient.Closed += wsClient_Closed;
                        this.ReadyState = WebSocketState.Connecting;

                        await tmpClient.ConnectAsync(
                            new Uri(string.Format("{0}://{1}:{2}/socket.io/1/websocket/{3}", wsScheme, Uri.Host,
                                Uri.Port, this.HandShake.SID)));
                        this.WsClient = tmpClient;

                        this._messageWriter = new DataWriter(tmpClient.OutputStream);

                        this.ReadyState = WebSocketState.Open;
                        this.ConnectionOpenEvent.Set();
                        this._socketHeartBeatTimer = ThreadPoolTimer.CreatePeriodicTimer(OnHeartBeatTimerCallback,
                            HandShake.HeartbeatInterval);

                        this.OnMessageEvent(new EventMessage {Event = "open"});
                        if (this.Opened != null)
                        {
                            try
                            {
                                this.Opened(this, EventArgs.Empty);
                            }
                            catch (Exception ex)
                            {
                                Debug.WriteLine(ex);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    this.ReadyState = WebSocketState.Closed;
                    this.ConnectionOpenEvent.Reset();
                    var status = WebSocketError.GetStatus(ex.GetBaseException().HResult);
                    var msg = "Connect threw an exception.  Socket status: " + status + ".  Error was: " + ex.Message;
                    Debug.WriteLine(msg);
                    this.OnErrorEvent(this, new ErrorEventArgs(msg, ex));
                    //throw;
                }
            }

            //}
        }

        //private void ConnectCompleted(IAsyncAction asyncInfo, AsyncStatus asyncStatus)
        //{
        //    if (asyncInfo.ErrorCode != null)
        //    {

        //        this.OnErrorEvent(null, new ErrorEventArgs("Socket error", asyncInfo.ErrorCode));
        //    }
        //    else
        //    {

        //    }
        //}

        public async Task<IEndPointClient> ConnectEndpointAsync(string endPoint, string userId = null, string password = null)
        {
            var nsClient = new EndPointClient(this, endPoint);
            await this.ConnectAsync(userId, password);
            this.Send(new ConnectMessage(endPoint));
            return nsClient;

            //var nsClient = new EndPointClient(this, endPoint);
            //var success = await this.Connect();
            //if (success)
            //{
            //    this.Send(new ConnectMessage(endPoint));
            //    return nsClient;
            //}
            //else
            //{
            //    return null;
            //}
        }

        protected async Task ReConnectAsync()
        {
            //await Task.Delay(5000);

            this._retryConnectionCount++;

            this.OnConnectionRetryAttemptEvent(this, EventArgs.Empty);

            this.CloseHeartBeatTimer();  // stop the heartbeat time
            this.CloseWebSocketClient(); // stop websocket

            await this.ConnectAsync();

            bool connected = ReadyState == WebSocketState.Open;
            //if (connected)
            //    connected = this.ConnectionOpenEvent.WaitOne(2000); // block while waiting for connection message
            
            Debug.WriteLine("\tRetry-Connection successful: {0}", connected);
            if (connected)
                this._retryConnectionCount = 0;
            else
            {
                // we didn't connect - try again until exhausted
                if (this._retryConnectionCount < this.RetryConnectionAttempts)
                {
                    await this.ReConnectAsync();
                }
                else
                {
                    this.Close();
                    this.OnSocketConnectionClosedEvent(this, EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// <para>Asynchronously calls the action delegate on event message notification</para>
        /// <para>Mimicks the Socket.IO client 'socket.on('name',function(data){});' pattern</para>
        /// <para>Reserved socket.io event names available: connect, disconnect, open, close, error, retry, reconnect  </para>
        /// </summary>
        /// <param name="eventName"></param>
        /// <param name="action"></param>
        /// <example>
        /// client.On("testme", (data) =>
        ///    {
        ///        Debug.WriteLine(data.ToJson());
        ///    });
        /// </example>
        public virtual void On(
            string eventName,
            Action<IMessage> action)
        {
            this.RegistrationManager.AddOnEvent(eventName, action);
        }
        public virtual void On(
            string eventName,
            string endPoint,
            Action<IMessage> action)
        {

            this.RegistrationManager.AddOnEvent(eventName, endPoint, action);
        }

        /// <summary>
        /// <para>Asynchronously sends payload using eventName</para>
        /// <para>payload must a string or Json Serializable</para>
        /// <para>Mimicks Socket.IO client 'socket.emit('name',payload);' pattern</para>
        /// <para>Do not use the reserved socket.io event names: connect, disconnect, open, close, error, retry, reconnect</para>
        /// </summary>
        /// <param name="eventName"></param>
        /// <param name="payload">must be a string or a Json Serializable object</param>
        /// <param name="endPoint"></param>
        /// <param name="callback"></param>
        /// <remarks>ArgumentOutOfRangeException will be thrown on reserved event names</remarks>
        public void Emit(string eventName, dynamic payload, string endPoint = "", Action<dynamic> callback = null)
        {
            string lceventName = eventName.ToLower();
            IMessage msg = null;
            switch (lceventName)
            {
                case "message":
                    if (payload is string)
                        msg = new TextMessage() { MessageText = payload };
                    else
                        msg = new JSONMessage(payload);
                    this.Send(msg);
                    break;
                case "connect":
                case "disconnect":
                case "open":
                case "close":
                case "error":
                case "retry":
                case "reconnect":
                    throw new ArgumentOutOfRangeException(eventName, "Event name is reserved by socket.io, and cannot be used by clients or servers with this message type");
                default:
                    if (!string.IsNullOrWhiteSpace(endPoint) && !endPoint.StartsWith("/"))
                        endPoint = "/" + endPoint;
                    msg = new EventMessage(eventName, payload, endPoint, callback);
                    if (callback != null)
                        this.RegistrationManager.AddCallBack(msg);

                    this.Send(msg);
                    break;
            }
        }

        ///// <summary>
        ///// <para>Asynchronously sends payload using eventName</para>
        ///// <para>payload must a string or Json Serializable</para>
        ///// <para>Mimicks Socket.IO client 'socket.emit('name',payload);' pattern</para>
        ///// <para>Do not use the reserved socket.io event names: connect, disconnect, open, close, error, retry, reconnect</para>
        ///// </summary>
        ///// <param name="eventName"></param>
        ///// <param name="payload">must be a string or a Json Serializable object</param>
        //public void Emit(string eventName, dynamic payload)
        //{
        //    this.Emit(eventName, payload, string.Empty, null);
        //}

        /// <summary>
        /// Queue outbound message
        /// </summary>
        /// <param name="msg"></param>
        public void Send(IMessage msg)
        {
            this.MessageQueueEmptyEvent.Reset();
            if (this._outboundQueue != null)
                this._outboundQueue.Add(msg.Encoded);
        }

        private void Send(string rawEncodedMessageText)
        {
            this.MessageQueueEmptyEvent.Reset();
            if (this._outboundQueue != null)
                this._outboundQueue.Add(rawEncodedMessageText);
        }

        /// <summary>
        /// if a registerd event name is found, don't raise the more generic Message event
        /// </summary>
        /// <param name="msg"></param>
        protected void OnMessageEvent(IMessage msg)
        {
            if (this.RegistrationManager == null)
                return;

            bool skip = false;
            if (!string.IsNullOrEmpty(msg.Event))
                skip = this.RegistrationManager.InvokeOnEvent(msg); // 

            var handler = this.Message;
            if (handler != null && !skip)
            {
                Debug.WriteLine("webSocket_OnMessage: {0}", msg.RawMessage);
                handler(this, new MessageEventArgs(msg));
            }
        }

        /// <summary>
        /// Close SocketIO4Net.Client and clear all event registrations 
        /// </summary>
        public void Close()
        {
            this._retryConnectionCount = 0; // reset for next connection cycle
            // stop the heartbeat time
            this.CloseHeartBeatTimer();

            // stop outbound messages
            this.CloseOutboundQueue();

            this.CloseWebSocketClient();

            if (this.RegistrationManager != null)
            {
                this.RegistrationManager.Dispose();
                this.RegistrationManager = null;
            }

        }

        protected void CloseHeartBeatTimer()
        {
            // stop the heartbeat timer
            if (this._socketHeartBeatTimer != null)
            {
                //this._socketHeartBeatTimer.Change(Timeout.Infinite, Timeout.Infinite);
                //this._socketHeartBeatTimer.Dispose();
                this._socketHeartBeatTimer.Cancel();

                this._socketHeartBeatTimer = null;
            }
        }
        protected void CloseOutboundQueue()
        {
            // stop outbound messages
            if (this._outboundQueue != null)
            {
                this._outboundQueue.CompleteAdding(); // stop adding any more items;
                this._dequeuOutBoundMsgTask.Wait(2000); // wait for dequeue thread to stop
                this._outboundQueue.Dispose();
                this._outboundQueue = null;
            }
        }
        protected void CloseWebSocketClient()
        {
            if (this.WsClient != null)
            {
                this.ReadyState = WebSocketState.Closing;
                this.ConnectionOpenEvent.Reset();
                

                //if (this.wsClient.State == WebSocketState.Connecting || this.wsClient.State == WebSocketState.Open)
                //{
                try
                {
                    // unwire events
                    this.WsClient.Closed -= this.wsClient_Closed;
                    this.WsClient.MessageReceived -= MessageReceived;
                    //this.wsClient.Error -= wsClient_Error;
                    //this.wsClient.Opened -= this.wsClient_OpenEvent;
                    this.WsClient.Close(1000, "?");
                }
                catch
                {
                    Debug.WriteLine("exception raised trying to close websocket: can safely ignore, socket is being closed");
                }
                //}
                this.WsClient = null;
            }
            this.ReadyState = WebSocketState.Closed;
        }

        // websocket client events - open, messages, errors, closing
        //private void wsClient_OpenEvent(object sender, EventArgs e)
        //{
        //    this._socketHeartBeatTimer = ThreadPoolTimer.CreatePeriodicTimer(OnHeartBeatTimerCallback, HandShake.HeartbeatInterval);
        //    this.ConnectionOpenEvent.Set();

        //    this.OnMessageEvent(new EventMessage() { Event = "open" });
        //    if (this.Opened != null)
        //    {
        //        try { this.Opened(this, EventArgs.Empty); }
        //        catch (Exception ex) { Debug.WriteLine(ex); }
        //    }

        //}

        /// <summary>
        /// Raw websocket messages from server - convert to message types and call subscribers of events and/or callbacks
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MessageReceived(MessageWebSocket messageWebSocket, MessageWebSocketMessageReceivedEventArgs args)
        {
            string msg;
            try
            {
                using (DataReader dr = args.GetDataReader())
                {
                    dr.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
                    msg = dr.ReadString(dr.UnconsumedBufferLength);
                }
            }
            catch (Exception ex)
            {
                if (ReadyState != WebSocketState.Closed)
                {
                    WebErrorStatus status = WebSocketError.GetStatus(ex.GetBaseException().HResult);
                    Debug.WriteLine("Could not get DataReader or couldn't read msg.  Status of socket is: " + status +
                                    ".  Error was: " + ex.Message);
                    if (this.WsClient != null)
                        this.WsClient.Close(1000, status.ToString());
                }
                return;
            }

            Debug.WriteLine("Received message: " + msg);

            IMessage iMsg = Messages.Message.Factory(msg);

            if (iMsg.Event == "responseMsg")
                Debug.WriteLine("InvokeOnEvent: {0}", iMsg.RawMessage);

            switch (iMsg.MessageType)
            {
                case SocketIOMessageTypes.Disconnect:
                    this.OnMessageEvent(iMsg);
                    if (string.IsNullOrWhiteSpace(iMsg.Endpoint)) // Disconnect the whole socket
                        this.Close();
                    break;
                case SocketIOMessageTypes.Heartbeat:
                    this.OnHeartBeatTimerCallback(null);
                    break;
                case SocketIOMessageTypes.Connect:
                case SocketIOMessageTypes.Message:
                case SocketIOMessageTypes.JSONMessage:
                case SocketIOMessageTypes.Event:
                case SocketIOMessageTypes.Error:
                    this.OnMessageEvent(iMsg);
                    break;
                case SocketIOMessageTypes.ACK:
                    this.RegistrationManager.InvokeCallBack(iMsg.AckId, iMsg.Json);
                    break;
                default:
                    Debug.WriteLine("unknown wsClient message Received...");
                    break;
            }
        }

        /// <summary>
        /// websocket has closed unexpectedly - retry connection
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void wsClient_Closed(IWebSocket sender, WebSocketClosedEventArgs args)
        {
            if (this.ReadyState == WebSocketState.Closed)
                return;
            this.ReadyState = WebSocketState.Closed;

            if (this._retryConnectionCount < this.RetryConnectionAttempts)
            {
                this.ConnectionOpenEvent.Reset();
                await this.ReConnectAsync();
            }
            else
            {
                this.Close();
                this.OnSocketConnectionClosedEvent(this, EventArgs.Empty);
            }
        }

        //private void wsClient_Error(object sender, SuperSocket.ClientEngine.ErrorEventArgs e)
        //{
        //    this.OnErrorEvent(sender, new ErrorEventArgs("SocketClient error", e.Exception));
        //}

        protected void OnErrorEvent(object sender, ErrorEventArgs e)
        {
            this.LastErrorMessage = e.Message;
            if (this.Error != null)
            {
                try { this.Error.Invoke(this, e); }
                catch { }
            }
            Debug.WriteLine("Error Event: {0}\r\n\t{1}", e.Message, e.Exception);
        }
        protected void OnSocketConnectionClosedEvent(object sender, EventArgs e)
        {
            if (this.SocketConnectionClosed != null)
            {
                try { this.SocketConnectionClosed(sender, e); }
                catch { }
            }
            Debug.WriteLine("SocketConnectionClosedEvent");
        }
        protected void OnConnectionRetryAttemptEvent(object sender, EventArgs e)
        {
            if (this.ConnectionRetryAttempt != null)
            {
                try { this.ConnectionRetryAttempt(sender, e); }
                catch (Exception ex) { Debug.WriteLine(ex); }
            }
            Debug.WriteLine("Attempting to reconnect: {0}", this._retryConnectionCount);
        }

        // Housekeeping
        protected void OnHeartBeatTimerCallback(object state)
        {
            if (this.ReadyState == WebSocketState.Open)
            {
                var msg = new Heartbeat();
                try
                {
                    if (this._outboundQueue != null && !this._outboundQueue.IsAddingCompleted)
                    {
                        this._outboundQueue.Add(msg.Encoded);
                        if (this.HeartBeatTimerEvent != null)
                        {
                            Task.Factory.StartNew(
                                () =>
                                {
                                    try
                                    {
                                        if (this.HeartBeatTimerEvent != null)
                                            this.HeartBeatTimerEvent(this, EventArgs.Empty);
                                    }
                                    catch
                                    {
                                        // Handle any exceptions that were thrown by the invoked method
                                        Debug.WriteLine("An event listener (event: HeartBeatTimerEvent) went kaboom!");
                                    }
                                });
                        }
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(string.Format("OnHeartBeatTimerCallback Error Event: {0}\r\n\t{1}", ex.Message, ex.InnerException));
                }
            }
        }

        /// <summary>
        /// While connection is open, dequeue and send messages to the socket server
        /// </summary>
        protected async Task DequeuOutboundMessages()
        {
            while (this._outboundQueue != null && !this._outboundQueue.IsAddingCompleted)
            {
                if (this.ReadyState == WebSocketState.Open)
                {
                    try
                    {
                        string msgString;
                        if (this._outboundQueue.TryTake(out msgString, 2000))
                        {
                            // Buffer any data we want to send.
                            _messageWriter.WriteString(msgString);

                            // Send the data as one complete message.
                            await _messageWriter.StoreAsync();

                            Debug.WriteLine("Sent message: {0}", msgString);
                        }
                        else
                            this.MessageQueueEmptyEvent.Set();
                    }
                    catch (Exception ex)
                    {
                        if (ReadyState != WebSocketState.Closed)
                        {
                            this.ConnectionOpenEvent.Reset();
                            WebErrorStatus status = WebSocketError.GetStatus(ex.GetBaseException().HResult);
                            Debug.WriteLine("Couldn't write to the outbound message queue.  Socket status: " + status +
                                            ".  Error was: " + ex.Message);
                            var messageWebSocket = this.WsClient;
                            if (messageWebSocket != null) messageWebSocket.Close(1000, status.ToString());
                        }
                    }
                }
                else
                {
                    this.ConnectionOpenEvent.WaitOne(2000); // wait for connection event
                }
            }
        }

        /// <summary>
        /// <para>Client performs an initial HTTP POST to obtain a SessionId (sid) assigned to a client, followed
        ///  by the heartbeat timeout, connection closing timeout, and the list of supported transports.</para>
        /// <para>The tansport and sid are required as part of the ws: transport connection</para>
        /// </summary>
        /// <param name="uri">http://localhost:3000</param>
        /// <returns>Handshake object with sid value</returns>
        /// <example>DownloadString: 13052140081337757257:15:25:websocket,htmlfile,xhr-polling,jsonp-polling</example>
        protected async Task<SocketIOHandshake> RequestHandshakeAsync(Uri uri)
        {
            string value = string.Empty;
            string errorText = string.Empty;
            SocketIOHandshake handshake = null;

            using (var http = new HttpClient())
            {
                try
                {
                    http.Timeout = new TimeSpan(0, 0, 10);
                    // #5 tkiley: The uri.Query is available in socket.io's handshakeData object during authorization
                    var resp = await http.GetAsync(new Uri(string.Format("{0}://{1}:{2}/socket.io/1/{3}", uri.Scheme, uri.Host, uri.Port, uri.Query)));
                    value = await resp.Content.ReadAsStringAsync();
                    // 13052140081337757257:15:25:websocket,htmlfile,xhr-polling,jsonp-polling
                    if (string.IsNullOrEmpty(value))
                        errorText = "Did not receive handshake string from server";
                }
                catch (Exception ex)
                {
                    errorText = string.Format("Error getting handsake from Socket.IO host instance: {0}", ex.Message);
                    //this.OnErrorEvent(this, new ErrorEventArgs(errMsg));
                }
            }
            if (string.IsNullOrEmpty(errorText))
                handshake = SocketIOHandshake.LoadFromString(value);
            else
            {
                handshake = new SocketIOHandshake();
                handshake.ErrorMessage = errorText;
            }

            return handshake;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        // The bulk of the clean-up code 
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // free managed resources
                this.Close();
                this.MessageQueueEmptyEvent.Dispose();
                this.ConnectionOpenEvent.Dispose();
            }

        }

        private DataWriter _messageWriter;
    }


}
